﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Ext.Net;
using Ext.Net.Utilities;
using DevExpress.Xpo;

namespace KamLib
{

    public abstract class EntitiesListLogic<TListData>
        : ListLogic<TListData>, IXpoSessionContainer
        where TListData : class, new()
    {
        public UnitOfWork db;
        public SessionConnection cn;
        UnitOfWork IXpoSessionContainer.Session { get { return db; } set { db = value; } }

        public override void Assign(UilNode src)
        {
            base.Assign(src);

            var isession = src as IXpoSessionContainer;
            if (isession == null) return;
            var session = isession.Session;
            if (session != null) db = session;
        }

        public override void Activate()
        {
            if (db == null)
                db = new UnitOfWork();
            if (cn == null)
                cn = new SessionConnection(db);
        }

        public override void Dispose()
        {
            if (db != null)
            {
                db.Dispose();
                db = null;
            }
            base.Dispose();
        }

        public IQueryable<T> Query<T>() { return db.Query<T>(); }
    }


    public abstract class EntityListLogic<TListData, TEntity> : EntitiesListLogic<TListData>
        where TListData : class, new()
        where TEntity : XPLiteObject
    {
        protected static System.Linq.Expressions.Expression<Func<TEntity, TListData>> selectExpression;

        /// <summary>
        /// Использовать Filter(Key.MasterID) при получении данных
        /// </summary>
        public virtual bool UseMaster { get { return Key.MasterID != null && Key.MasterID.Count > 0; } }

        /// <summary>
        /// Выражение вида:
        /// a => new FormData { Field1 = a.Parent.Field1, ... }
        /// Используется в Select для полей, которые берутся из других Entities
        /// </summary>
        protected static System.Linq.Expressions.Expression<Func<TEntity, TListData>> SelectInit;

        public override IEnumerable<TListData> SelectEntities(WhereArgs e)
        {
            return Select(e, SelectInit);
        }

        protected IEnumerable<TListData> Select(WhereArgs e, System.Linq.Expressions.Expression<Func<TEntity, TListData>> init)
        {
            Activate();
            var tbl = UseMaster ? db.Query<TEntity>().Filter(Key.MasterID) : db.Query<TEntity>();

            var calcSortFields = new WhereArgSortFields();
            foreach (var field in e.SortFields)
            {
                var sortProp = field.Name.IsNotEmpty() ? typeof(TListData).GetPropertyOrField(field.Name) : null;
                if (sortProp == null)
                    e.SortFields.Remove(field);
                else
                {
                    var converts = (ConvertAttribute[])sortProp.GetCustomAttributes(typeof(ConvertAttribute), true);
                    foreach (var convert in converts)
                    {
                        if (convert.IsCalculate)
                            calcSortFields.Register(field);
                    }
                }
            }
            e.SortFields.RemoveAll(calcSortFields.Contains);
            //var list1 = tbl.Select(ref selectExpression, init).ToArray();
            var list = tbl.Select(ref selectExpression, init).Where(e).ToList().Sort(calcSortFields, e.UseOrderAsThen);

            return list;
        }

        public override void SaveLocalChanges(IEnumerable<object> newData)
        {
            var currentData = UseMaster
                ? db.Query<TEntity>().Filter(Key.MasterID).ToList()
                : db.Query<TEntity>().ToList();

            var keyCount = KeyFields.Length;
            var ekeyProps = EntityHelper.GetKeyFields<TEntity>();
            var keyProps = new MemberInfo[keyCount];

            for (var i = 0; i < keyCount; ++i)
            {
                keyProps[i] = typeof(TListData).GetPropertyOrField(KeyFields[i]);
            }

            foreach (var ndata in newData)
            {
                var isnew = true;
                for (var i = 0; i < keyCount; ++i)
                {
                    if (keyProps[i].GetValue(ndata, null) != null)
                    {
                        isnew = false;
                        break;
                    }
                }

                TEntity cdata;

                if (isnew)
                    cdata = (TEntity)Activator.CreateInstance(typeof(TEntity), db);
                else
                {
                    var keys = new object[keyCount];
                    for (var i = 0; i < keyCount; ++i)
                    {
                        keys[i] = keyProps[i].GetValue(ndata, null);
                    }

                    var ind = currentData.IndexOf(a =>
                    {
                        if (a == null) return false;
                        for (var i = 0; i < keyCount; ++i)
                        {
                            var key = keys[i];
                            var ekey = ekeyProps[i].GetValue(a, null);
                            if (!Equals(key, ekey)) return false;
                        }
                        return true;
                    });

                    if (ind >= 0)
                    {
                        cdata = currentData[ind];
                        currentData[ind] = null;
                    }
                    else
                    {
                        cdata = (TEntity)Activator.CreateInstance(typeof(TEntity), db); 
                    }
                }


                if (Key.MasterID != null) cdata.CopyFrom(Key.MasterID);
                cdata.CopyFrom(ndata);

                //cdata.Save();
            }

            foreach (var cdata in currentData)
            {
                if (cdata != null)
                    cdata.Delete();
            }

        }

    }


    public abstract class XpoSqlListLogic : SqlListLogic, IXpoSessionContainer
    {
        public UnitOfWork db;
        UnitOfWork IXpoSessionContainer.Session { get { return db; } set { db = value; } }

        public override void Assign(UilNode src)
        {
            base.Assign(src);

            var isession = src as IXpoSessionContainer;
            if (isession == null) return;
            var session = isession.Session;
            if (session != null) db = session;
        }

        public override void Activate()
        {
            if (db == null)
                db = new UnitOfWork();
            if (cn == null)
                cn = new SessionConnection(db);
        }

        public override void Dispose()
        {
            if (db != null)
            {
                db.Dispose();
                db = null;
            }
            cn = null;
            base.Dispose();
        }        
    }

}